package info.hexin.ioc.loader.impl;

import info.hexin.ioc.IocBean;
import info.hexin.ioc.Property;
import info.hexin.ioc.annotation.Bean;
import info.hexin.ioc.annotation.Inject;
import info.hexin.ioc.annotation.Scope;
import info.hexin.ioc.loader.Loader;
import info.hexin.lang.Exceptions;
import info.hexin.lang.Strings;
import info.hexin.lang.reflect.Wrap;
import info.hexin.log.Log;
import info.hexin.log.Logs;

import java.io.File;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 解析已经标记注解的类
 * 
 * @author hexin
 * 
 */
public class AnnotationLoader implements Loader {

    private static final Log log = Logs.get();
    private Map<String, IocBean> beanMap = new HashMap<String, IocBean>();

    public AnnotationLoader(String... packages) {
        loadPackages(packages);
        injectField();
    }

    /**
     * 找到所有注解的类, 并实现单例
     * 
     * @param packages
     */
    private void loadPackages(String... packages) {
        for (String packagePath : packages) {
            log.info(" 将要加载包 >>>>> " + packagePath);
            String filePath = ClassLoader.getSystemResource("").getPath() + packagePath.replace(".", "\\");
            File file = new File(filePath);
            if (!file.exists()) {
                throw Exceptions.make("packagePath >>> " + packagePath + " is not exists!!");
            }
            String[] ss = file.list();
            for (String fileName : ss) {
                if (fileName.endsWith(".class")) {
                    String clazzName = packagePath + "." + fileName.substring(0, fileName.indexOf("."));
                    try {
                        Class<?> clazz = Class.forName(clazzName);
                        Bean component = clazz.getAnnotation(Bean.class);
                        if (component != null) {
                            if (log.isInfoEnabled()) {
                                log.info("loading Component classs >>> {}", clazzName);
                            }
                            IocBean iocBean = new IocBean();
                            iocBean.setClazz(clazz);
                            String name = component.name();
                            if (Strings.isBlank(name)) {
                                name = Strings.lowerFirst(clazz.getSimpleName());
                            }
                            iocBean.setName(name);
                            Scope scope = component.scope();
                            iocBean.setScope(scope);
                            iocBean.setInstance(iocBean.newInstance());
                            beanMap.put(name, iocBean);
                            log.info("name >>>>> " + iocBean.getName() + " 增加到context中");

                            Field[] fields = clazz.getDeclaredFields();
                            for (Field field : fields) {
                                Inject inject = field.getAnnotation(Inject.class);
                                String byName = inject.name();
                                String value = inject.value();
                                if (inject != null) {
                                    Class<?> fieldClazz = field.getType();
                                    if (Strings.isBlank(byName)) {
                                        String simpleName = fieldClazz.getSimpleName();
                                        byName = Strings.lowerFirst(simpleName);
                                    }

                                    Property property = new Property();
                                    property.setField(field);
                                    property.setRef(byName);
                                    property.setValue(value);
                                    iocBean.addProperty(property);
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error("loading error >>> ", e);
                    }
                }
            }
        }
    }

    /**
     * 进行属性注入
     * 
     */
    public void injectField() {
        for (Map.Entry<String, IocBean> e : beanMap.entrySet()) {
            IocBean iocBean = e.getValue();
            Class<?> clazz = iocBean.getClazz();
            List<Property> propertiesList = iocBean.getPropertiesList();
            for (Property property : propertiesList) {
                Field field = property.getField();
                Class<?> fieldClazz = field.getType();
                Wrap.setFieldPublicAccessible(field);
                Object o = null;
                if (Strings.isNotBlank(property.getValue())) {
                    o = property.getValue();
                } else {
                    String byName = property.getRef();
                    IocBean injectBean = beanMap.get(byName);
                    if (injectBean == null) {
                        // 最常见的是给用接口来接受注入的实例
                        for (Map.Entry<String, IocBean> entry : beanMap.entrySet()) {
                            IocBean tmpIocBean = entry.getValue();
                            Object object = tmpIocBean.getInstance();
                            if (fieldClazz.isInstance(object)) {
                                injectBean = tmpIocBean;
                            }
                        }
                    }

                    if (injectBean == null) {
                        throw Exceptions.make("请检查" + fieldClazz + " 注解配置是否正确" + clazz.getName() + "中Inject注解是否一致。");
                    }

                    if (injectBean.isSingleton()) {
                        o = injectBean.getInstance();
                    } else {
                        o = injectBean.newInstance();
                    }
                }
                Wrap.fieldAssignment(iocBean.getInstance(), field, o);
            }
        }
    }

    @Override
    public Map<String, IocBean> getBeans() {
        return beanMap;
    }
}
